/*******************************************************************************
* Copyright (c) 2012 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.quickfix;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
import org.eclipse.jdt.internal.ui.text.correction.proposals.ModifierChangeCorrectionProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewVariableCorrectionProposal;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
import org.eclipse.wst.xml.core.internal.document.AttrImpl;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.config.core.IConfigEditor;
import org.springframework.ide.eclipse.config.core.schemas.BeansSchemaConstants;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.ide.eclipse.core.model.IResourceModelElement;
import org.springframework.ide.eclipse.quickfix.proposals.CreateNewMethodQuickFixProposal;
import org.springframework.ide.eclipse.quickfix.proposals.QuickfixReflectionUtils;
import org.springframework.ide.eclipse.quickfix.validator.BeanValidator;
import org.springsource.ide.eclipse.commons.core.StatusHandler;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
/**
* Utility class with helper methods for calculating quick fixes for bean
* configuration files.
* @author Terry Denney
* @author Leo Dos Santos
* @author Christian Dupuis
* @since 2.0
*/
public class QuickfixUtils {
private static class ASTFinder extends ASTVisitor {
private MethodInvocation methodInvo;
private ClassInstanceCreation constInvo;
private Assignment assignment;
private MethodDeclaration methodDecl;
private IMethod methodToMatch;
private String classNameToMatch;
private TypeDeclaration typeDecl;
private ASTFinder() {
}
private ASTFinder(IMethod methodToMatch) {
this.methodToMatch = methodToMatch;
}
private ASTFinder(String classNameToMatch) {
int pos = classNameToMatch.lastIndexOf(".");
if (pos > 0) {
this.classNameToMatch = classNameToMatch.substring(pos + 1);
}
else {
this.classNameToMatch = classNameToMatch;
}
}
private ClassInstanceCreation getConstructorInvocation() {
return constInvo;
}
private SimpleName getFieldAccess() {
if (assignment == null) {
return null;
}
Expression lhs = assignment.getLeftHandSide();
if (lhs instanceof SimpleName) {
return (SimpleName) lhs;
}
if (lhs instanceof QualifiedName) {
QualifiedName qn = (QualifiedName) lhs;
return qn.getName();
}
return null;
}
private MethodDeclaration getMethodDeclaration() {
return methodDecl;
}
private MethodInvocation getMethodInvocation() {
return methodInvo;
}
private TypeDeclaration getTypeDeclaration() {
return typeDecl;
}
@Override
public boolean visit(Assignment node) {
this.assignment = node;
return false;
}
@Override
public boolean visit(ClassInstanceCreation node) {
this.constInvo = node;
return false;
}
@Override
public boolean visit(MethodDeclaration node) {
if (methodToMatch != null) {
if (node.getName().getFullyQualifiedName().equals(methodToMatch.getElementName())
&& node.parameters().size() == methodToMatch.getNumberOfParameters()) {
this.methodDecl = node;
return false;
}
}
return super.visit(node);
}
@Override
public boolean visit(MethodInvocation node) {
this.methodInvo = node;
return false;
}
@Override
public boolean visit(TypeDeclaration node) {
if (node.getName().getFullyQualifiedName().equals(classNameToMatch)) {
this.typeDecl = node;
return false;
}
return true;
}
}
// private static QuickfixSupport nodeQuickfixSupport;
private static QuickfixSupport attributeQuickfixSupport;
private static ASTParser createASTParser(IJavaProject javaProject, ICompilationUnit cu, int kind) {
ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setKind(kind);
parser.setSource(cu);
parser.setResolveBindings(true);
parser.setProject(javaProject);
return parser;
}
public static void createConstructor(IDocument document, IType targetType, List<String> constructorArgClassNames,
IJavaProject javaProject) {
ICompilationUnit cu = targetType.getCompilationUnit();
if (cu == null) {
return;
}
ITypeBinding typeBinding = QuickfixUtils.getTargetTypeBinding(javaProject, targetType);
String className = targetType.getFullyQualifiedName();
ClassInstanceCreation invocationNode = QuickfixUtils.getMockConstructorInvocation(className,
constructorArgClassNames.toArray(new String[constructorArgClassNames.size()]));
List<Expression> arguments = QuickfixUtils.getArguments(invocationNode);
Object constructorProposal = QuickfixReflectionUtils.createNewMethodProposal(targetType.getElementName(), cu,
invocationNode, arguments, typeBinding, 0, null);
QuickfixReflectionUtils.applyProposal(constructorProposal, document);
};
public static ModifierChangeCorrectionProposal createModifierChangeCorrectionProposal(String className,
String fieldName, IJavaProject javaProject, String displayString, boolean isStatic) {
IType type = JdtUtils.getJavaType(javaProject.getProject(), className);
IField field = type.getField(fieldName);
IBinding binding = getBinding(javaProject, field, type.getCompilationUnit(), ASTParser.K_COMPILATION_UNIT);
SimpleName fieldNameNode = getMockFieldAccess(className, fieldName, isStatic);
return new ModifierChangeCorrectionProposal(fieldName, type.getCompilationUnit(), binding, fieldNameNode,
Modifier.STATIC, 0, 5, null);
}
public static NewVariableCorrectionProposal createNewVariableCorrectionProposal(String className, String fieldName,
IJavaProject javaProject, String displayString, boolean isStatic) {
IType type = JdtUtils.getJavaType(javaProject.getProject(), className);
ITypeBinding typeBinding = getTargetTypeBinding(javaProject, type);
SimpleName fieldNameNode = getMockFieldAccess(className, fieldName, isStatic);
return new NewVariableCorrectionProposal(displayString, type.getCompilationUnit(),
NewVariableCorrectionProposal.FIELD, fieldNameNode, typeBinding, 100,
JavaPluginImages.get(JavaPluginImages.IMG_FIELD_PUBLIC));
}
@SuppressWarnings("unchecked")
public static List<Expression> getArguments(ClassInstanceCreation constructorInvocation) {
List<Expression> arguments = new ArrayList<Expression>();
for (Iterator<Expression> argumentIter = constructorInvocation.arguments().iterator(); argumentIter.hasNext();) {
Expression argumentExpression = argumentIter.next();
arguments.add(argumentExpression);
}
return arguments;
}
@SuppressWarnings("unchecked")
public static List<Expression> getArguments(MethodInvocation methodInvocation) {
List<Expression> arguments = new ArrayList<Expression>();
for (Iterator<Expression> argumentIter = methodInvocation.arguments().iterator(); argumentIter.hasNext();) {
Expression argumentExpression = argumentIter.next();
arguments.add(argumentExpression);
}
return arguments;
}
private static synchronized QuickfixSupport getAttributeQuickfixSupport() {
if (attributeQuickfixSupport == null) {
attributeQuickfixSupport = new AttributeQuickfixSupport();
}
return attributeQuickfixSupport;
}
private static IBinding getBinding(IJavaProject javaProject, IJavaElement javaElement, ICompilationUnit cu, int kind) {
ASTParser bindingParser = createASTParser(javaProject, cu, kind);
IBinding[] bindings = bindingParser.createBindings(new IJavaElement[] { javaElement },
new NullProgressMonitor());
if (bindings != null && bindings.length > 0) {
return bindings[0];
}
return null;
}
public static CompilationUnit getCompilationUnitAST(IJavaProject javaProject, ICompilationUnit cu) {
ASTParser parser = createASTParser(javaProject, cu, ASTParser.K_COMPILATION_UNIT);
ASTNode ast = parser.createAST(null);
if (ast instanceof CompilationUnit) {
return (CompilationUnit) ast;
}
return null;
}
public static String getConfigName(IResource resource) {
return resource.getProjectRelativePath().toString();
}
public static IDocument getDocument(IMarker marker) {
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
IEditorPart editor = null;
if (marker.getResource() instanceof IFile) {
FileEditorInput fileInput = new FileEditorInput((IFile) marker.getResource());
editor = page.findEditor(fileInput);
if (editor == null) {
try {
editor = IDE.openEditor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(),
(IFile) marker.getResource());
}
catch (PartInitException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (editor instanceof IConfigEditor) {
IConfigEditor configEditor = (IConfigEditor) editor;
return configEditor.getTextViewer().getDocument();
}
else if (editor instanceof MultiPageEditorPart) {
MultiPageEditorPart multiEditor = (MultiPageEditorPart) editor;
IEditorPart[] editors = multiEditor.findEditors(fileInput);
for (IEditorPart e : editors) {
if (e instanceof StructuredTextEditor) {
return ((StructuredTextEditor) e).getDocumentProvider().getDocument(fileInput);
}
}
}
else if (editor instanceof AbstractDecoratedTextEditor) {
return ((AbstractDecoratedTextEditor) editor).getDocumentProvider().getDocument(fileInput);
}
}
return null;
}
public static IDOMNode getEnclosingBeanNode(IDOMNode node) {
if (node == null) {
return null;
}
String localName = node.getLocalName();
if (localName != null && localName.equals(BeansSchemaConstants.ELEM_BEAN)) {
return node;
}
Node parentNode = node.getParentNode();
if (parentNode instanceof IDOMNode) {
return getEnclosingBeanNode((IDOMNode) parentNode);
}
return null;
}
public static IMethodBinding getMethodBinding(IJavaProject javaProject, IMethod method) {
IBinding binding = getBinding(javaProject, method, method.getCompilationUnit(),
ASTParser.K_CLASS_BODY_DECLARATIONS);
if (binding != null && binding instanceof IMethodBinding) {
return (IMethodBinding) binding;
}
return null;
}
public static MethodDeclaration getMethodDecl(IMethod method) {
ASTParser parser = createASTParser(method.getJavaProject(), method.getCompilationUnit(),
ASTParser.K_COMPILATION_UNIT);
ASTNode ast = parser.createAST(null);
if (ast instanceof CompilationUnit) {
CompilationUnit cu = (CompilationUnit) ast;
ASTFinder visitor = new ASTFinder(method);
cu.accept(visitor);
return visitor.getMethodDeclaration();
}
return null;
}
public static ClassInstanceCreation getMockConstructorInvocation(String className, String[] argTypes) {
String varSetup = "";
String methodArgs = "";
for (int i = 0; i < argTypes.length; i++) {
String argType = argTypes[i];
varSetup += argType + " arg" + i + ";";
if (i > 0) {
methodArgs += ",";
}
methodArgs += "arg" + i;
}
String mockConstructorCode = varSetup + "new " + className + "(" + methodArgs + ");";
return getParsedExpressionFinder(mockConstructorCode, false).getConstructorInvocation();
}
public static SimpleName getMockFieldAccess(String className, String fieldName, boolean isStatic) {
String code = fieldName + "=null;";
if (isStatic) {
code = "public static void stub() {" + code + "}";
}
return getParsedExpressionFinder(code, isStatic).getFieldAccess();
}
public static MethodInvocation getMockMethodInvocation(String methodName, String[] argTypes, String returnType,
boolean isStatic) {
String varSetup = "";
String methodArgs = "";
for (int i = 0; i < argTypes.length; i++) {
String argType = argTypes[i];
varSetup += argType + " arg" + i + ";";
if (i > 0) {
methodArgs += ",";
}
methodArgs += "arg" + i;
}
String mockMethodInvocationCode = methodName + "(" + methodArgs + ");";
if (returnType != null) {
mockMethodInvocationCode = varSetup + returnType + " xxx = " + mockMethodInvocationCode;
}
if (isStatic) {
mockMethodInvocationCode = "static {" + mockMethodInvocationCode + "}";
}
return getParsedExpressionFinder(mockMethodInvocationCode, isStatic).getMethodInvocation();
}
public static CreateNewMethodQuickFixProposal getNewMethodQuickFixProposal(String methodName, String returnType,
String[] methodParamTypes, IJavaProject javaProject, String className, int offset, int length, String text,
boolean missingEndQuote, boolean isStatic, String elementType) {
IType targetType = null;
try {
targetType = javaProject.findType(className);
}
catch (JavaModelException e) {
StatusHandler.log(e.getStatus());
MessageDialog.openError(Display.getDefault().getActiveShell(), "Quick Asssit Error",
"Could not find class '" + className + "'");
return null;
}
if (targetType == null) {
StatusHandler
.log(new Status(Status.ERROR, Activator.PLUGIN_ID, "Could not find class '" + className + "'"));
MessageDialog.openError(Display.getDefault().getActiveShell(), "Quick Fix Error", "Could not find class '"
+ className + "'");
return null;
}
MethodInvocation mockMethodInvocation = getMockMethodInvocation(methodName, methodParamTypes, returnType,
isStatic);
if (mockMethodInvocation == null) {
return null;
}
ITypeBinding typeBinding = getTargetTypeBinding(javaProject, targetType);
List<Expression> arguments = getArguments(mockMethodInvocation);
ICompilationUnit cu = targetType.getCompilationUnit();
if (cu == null || typeBinding == null || arguments == null) {
return null;
}
return new CreateNewMethodQuickFixProposal(offset, length, "Add missing " + elementType + " \'" + text
+ "\' in class \'" + className + "\'", cu, mockMethodInvocation, arguments, typeBinding, 0,
missingEndQuote);
}
private static ASTFinder getParsedExpressionFinder(String toBeParsed, boolean isStatic) {
ASTParser fooParser = ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
if (isStatic) {
fooParser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS);
}
else {
fooParser.setKind(ASTParser.K_STATEMENTS);
}
fooParser.setSource(toBeParsed.toCharArray());
fooParser.setResolveBindings(true);
ASTNode parsedAstNode = fooParser.createAST(null);
ASTFinder visitor = new ASTFinder();
parsedAstNode.accept(visitor);
return visitor;
}
public static ITypeBinding getTargetTypeBinding(IJavaProject javaProject, IType targetType) {
IBinding binding = getBinding(javaProject, targetType, targetType.getCompilationUnit(),
ASTParser.K_COMPILATION_UNIT);
if (binding != null && binding instanceof ITypeBinding) {
return (ITypeBinding) binding;
}
return null;
}
public static BodyDeclaration getTypeDecl(String className, ICompilationUnit compUnit) {
ASTParser parser = createASTParser(compUnit.getJavaProject(), compUnit, ASTParser.K_COMPILATION_UNIT);
ASTNode ast = parser.createAST(null);
if (ast instanceof CompilationUnit) {
CompilationUnit cu = (CompilationUnit) ast;
ASTFinder visitor = new ASTFinder(className);
cu.accept(visitor);
return visitor.getTypeDeclaration();
}
return null;
}
private static Set<BeanValidator> getValidators(Node node, Attr attr) {
return getAttributeQuickfixSupport().getQuickfixValidators(node, attr);
}
public static boolean validateAttribute(IBeansConfig config, IResourceModelElement contextElement,
AttrImpl attrImpl, IDOMNode parent, IReporter reporter, boolean reportError,
BeansEditorValidator editorValidator) {
Set<BeanValidator> beanValidators = getValidators(parent, attrImpl);
boolean errorFound = false;
for (BeanValidator beanValidator : beanValidators) {
errorFound |= beanValidator.validateAttributeWithConfig(config, contextElement, attrImpl, parent, reporter,
reportError, editorValidator);
}
return errorFound;
}
}